<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
  <meta name="theme-color" content="#222">
  <meta name="generator" content="Hexo 5.4.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

  <link rel="stylesheet" href="/css/main.css">



  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.2/css/all.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.1.1/animate.min.css">

  <script class="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {
      "hostname": "holidaypenguin.gitee.io",
      "root": "/",
      "images": "/images",
      "scheme": "Mist",
      "version": "8.2.2",
      "exturl": false,
      "sidebar": {
        "position": "right",
        "display": "always",
        "padding": 18,
        "offset": 12
      },
      "copycode": true,
      "bookmark": {
        "enable": false,
        "color": "#222",
        "save": "auto"
      },
      "fancybox": false,
      "mediumzoom": false,
      "lazyload": false,
      "pangu": false,
      "comments": {
        "style": "tabs",
        "active": null,
        "storage": true,
        "lazyload": false,
        "nav": null
      },
      "motion": {
        "enable": true,
        "async": true,
        "transition": {
          "post_block": "fadeIn",
          "post_header": "fadeInDown",
          "post_body": "fadeInDown",
          "coll_header": "fadeInLeft",
          "sidebar": "slideUpIn"
        }
      },
      "prism": false,
      "i18n": {
        "placeholder": "搜索...",
        "empty": "没有找到任何搜索结果：${query}",
        "hits_time": "找到 ${hits} 个搜索结果（用时 ${time} 毫秒）",
        "hits": "找到 ${hits} 个搜索结果"
      },
      "path": "/search.xml",
      "localsearch": {
        "enable": true,
        "trigger": "auto",
        "top_n_per_article": 1,
        "unescape": false,
        "preload": false
      }
    };
  </script>
  <meta property="og:type" content="article">
  <meta property="og:title" content="前端数据埋点">
  <meta property="og:url" content="https://holidaypenguin.gitee.io/blob/2021-09-14-front-end-data-embedding-point/index.html">
  <meta property="og:site_name" content="HolidayPenguin">
  <meta property="og:locale" content="zh_CN">
  <meta property="og:image" content="https://visitor-badge.glitch.me/badge?page_id=holidaypenguin.gitee.io">
  <meta property="og:image" content="https://holidaypenguin.gitee.io/images/FrontEnd/point/84e6af0463f742ccbd2e98a0578aeeb3tplv-k3u1fbpfcp-watermark.awebp">
  <meta property="og:image" content="https://holidaypenguin.gitee.io/images/FrontEnd/point/258749b333514ff5b465585dde8180datplv-k3u1fbpfcp-watermark.awebp">
  <meta property="og:image" content="https://holidaypenguin.gitee.io/images/FrontEnd/point/858b21fcc1af46f4b897c9964fde9e4etplv-k3u1fbpfcp-watermark.awebp">
  <meta property="og:image" content="https://holidaypenguin.gitee.io/images/FrontEnd/point/234df87c541a44e4826fbd744d894316tplv-k3u1fbpfcp-watermark.awebp">
  <meta property="og:image" content="https://holidaypenguin.gitee.io/images/FrontEnd/point/785fe910dc77480b815fcc8e40e67526tplv-k3u1fbpfcp-watermark.awebp">
  <meta property="og:image" content="https://holidaypenguin.gitee.io/images/FrontEnd/point/8b4b4d8b2afc4b1dbd32a50281a9e86dtplv-k3u1fbpfcp-watermark.awebp">
  <meta property="article:published_time" content="2021-09-14T06:48:07.000Z">
  <meta property="article:modified_time" content="2021-09-14T06:48:07.000Z">
  <meta property="article:author" content="holidaypenguin">
  <meta property="article:tag" content="FrontEnd">
  <meta property="article:tag" content="埋点">
  <meta name="twitter:card" content="summary">
  <meta name="twitter:image" content="https://visitor-badge.glitch.me/badge?page_id=holidaypenguin.gitee.io">


  <link rel="canonical" href="https://holidaypenguin.gitee.io/blob/2021-09-14-front-end-data-embedding-point/">


  <script class="page-configurations">
    // https://hexo.io/docs/variables.html
    CONFIG.page = {
      sidebar: "",
      isHome: false,
      isPost: true,
      lang: 'zh-CN'
    };
  </script>
  <title>前端数据埋点 | HolidayPenguin</title>





  <noscript>
    <style>
      body {
        margin-top: 2rem;
      }

      .use-motion .menu-item,
      .use-motion .sidebar,
      .use-motion .post-block,
      .use-motion .pagination,
      .use-motion .comments,
      .use-motion .post-header,
      .use-motion .post-body,
      .use-motion .collection-header {
        visibility: visible;
      }

      .use-motion .header,
      .use-motion .site-brand-container .toggle,
      .use-motion .footer {
        opacity: initial;
      }

      .use-motion .site-title,
      .use-motion .site-subtitle,
      .use-motion .custom-logo-image {
        opacity: initial;
        top: initial;
      }

      .use-motion .logo-line {
        transform: scaleX(1);
      }

      .search-pop-overlay,
      .sidebar-nav {
        display: none;
      }

      .sidebar-panel {
        display: block;
      }
    </style>
  </noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner">
        <div class="site-brand-container">
          <div class="site-nav-toggle">
            <div class="toggle" aria-label="切换导航栏" role="button">
              <span class="toggle-line"></span>
              <span class="toggle-line"></span>
              <span class="toggle-line"></span>
            </div>
          </div>

          <div class="site-meta">

            <a href="/" class="brand" rel="start">
              <i class="logo-line"></i>
              <h1 class="site-title">HolidayPenguin</h1>
              <i class="logo-line"></i>
            </a>
          </div>

          <div class="site-nav-right">
            <div class="toggle popup-trigger">
              <i class="fa fa-search fa-fw fa-lg"></i>
            </div>
          </div>
        </div>



        <nav class="site-nav">
          <ul class="main-menu menu">
            <li class="menu-item menu-item-home"><a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a></li>
            <li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a></li>
            <li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a></li>
            <li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a></li>
            <li class="menu-item menu-item-search">
              <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
              </a>
            </li>
          </ul>
        </nav>



        <div class="search-pop-overlay">
          <div class="popup search-popup">
            <div class="search-header">
              <span class="search-icon">
                <i class="fa fa-search"></i>
              </span>
              <div class="search-input-container">
                <input autocomplete="off" autocapitalize="off" maxlength="80" placeholder="搜索..." spellcheck="false" type="search" class="search-input">
              </div>
              <span class="popup-btn-close" role="button">
                <i class="fa fa-times-circle"></i>
              </span>
            </div>
            <div class="search-result-container no-result">
              <div class="search-result-icon">
                <i class="fa fa-spinner fa-pulse fa-5x"></i>
              </div>
            </div>

          </div>
        </div>

      </div>


      <div class="toggle sidebar-toggle" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
      </div>

      <aside class="sidebar">

        <div class="sidebar-inner sidebar-nav-active sidebar-toc-active">
          <ul class="sidebar-nav">
            <li class="sidebar-nav-toc">
              文章目录
            </li>
            <li class="sidebar-nav-overview">
              站点概览
            </li>
          </ul>

          <div class="sidebar-panel-container">
            <!--noindex-->
            <div class="post-toc-wrap sidebar-panel">
              <div class="post-toc animated">
                <ol class="nav">
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF%E5%9F%8B%E7%82%B9"><span class="nav-number">1.</span> <span class="nav-text">什么是埋点</span></a>
                    <ol class="nav-child">
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%9F%8B%E7%82%B9"><span class="nav-number">1.1.</span> <span class="nav-text">埋点</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%BB%E6%B5%81%E6%96%B9%E6%A1%88"><span class="nav-number">1.2.</span> <span class="nav-text">主流方案</span></a>
                        <ol class="nav-child">
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%97%A0%E7%97%95%E5%9F%8B%E7%82%B9%EF%BC%88%E5%85%A8%E5%9F%8B%E7%82%B9%EF%BC%89"><span class="nav-number">1.2.1.</span> <span class="nav-text">无痕埋点（全埋点）</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%AF%E8%A7%86%E5%8C%96%E5%9F%8B%E7%82%B9"><span class="nav-number">1.2.2.</span> <span class="nav-text">可视化埋点</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%89%8B%E5%8A%A8%E5%9F%8B%E7%82%B9"><span class="nav-number">1.2.3.</span> <span class="nav-text">手动埋点</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%9F%8B%E7%82%B9%E6%96%B9%E6%A1%88%E6%AF%94%E8%BE%83"><span class="nav-number">1.2.4.</span> <span class="nav-text">埋点方案比较</span></a></li>
                        </ol>
                      </li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%B8%B8%E8%A7%81%E5%9F%8B%E7%82%B9%E5%B1%9E%E6%80%A7"><span class="nav-number">1.3.</span> <span class="nav-text">常见埋点属性</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%B8%B8%E8%A7%81%E5%9F%8B%E7%82%B9%E4%BA%8B%E4%BB%B6"><span class="nav-number">1.4.</span> <span class="nav-text">常见埋点事件</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%9F%8B%E7%82%B9%E7%B1%BB%E5%9E%8B"><span class="nav-number">1.5.</span> <span class="nav-text">埋点类型</span></a></li>
                    </ol>
                  </li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%95%B0%E6%8D%AE%E5%9F%8B%E7%82%B9"><span class="nav-number">2.</span> <span class="nav-text">数据埋点</span></a></li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%80%A7%E8%83%BD%E5%9F%8B%E7%82%B9"><span class="nav-number">3.</span> <span class="nav-text">性能埋点</span></a>
                    <ol class="nav-child">
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#Performance-timing"><span class="nav-number">3.1.</span> <span class="nav-text">Performance.timing</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%B8%B8%E8%A7%81%E6%80%A7%E8%83%BD%E6%8C%87%E6%A0%87"><span class="nav-number">3.2.</span> <span class="nav-text">常见性能指标</span></a>
                        <ol class="nav-child">
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#FP"><span class="nav-number">3.2.1.</span> <span class="nav-text">FP</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#FCP"><span class="nav-number">3.2.2.</span> <span class="nav-text">FCP</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#FMP"><span class="nav-number">3.2.3.</span> <span class="nav-text">FMP</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#TTI"><span class="nav-number">3.2.4.</span> <span class="nav-text">TTI</span></a></li>
                        </ol>
                      </li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E6%8C%87%E6%A0%87"><span class="nav-number">3.3.</span> <span class="nav-text">资源加载指标</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%85%B6%E4%BB%96%E6%8C%87%E6%A0%87%E8%AE%A1%E7%AE%97%E6%96%B9%E5%BC%8F"><span class="nav-number">3.4.</span> <span class="nav-text">其他指标计算方式</span></a></li>
                    </ol>
                  </li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%BC%82%E5%B8%B8%E5%9F%8B%E7%82%B9"><span class="nav-number">4.</span> <span class="nav-text">异常埋点</span></a>
                    <ol class="nav-child">
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%B5%84%E6%BA%90%E5%8A%A0%E8%BD%BD%E9%94%99%E8%AF%AF"><span class="nav-number">4.1.</span> <span class="nav-text">资源加载错误</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#js-%E6%89%A7%E8%A1%8C%E9%94%99%E8%AF%AF"><span class="nav-number">4.2.</span> <span class="nav-text">js 执行错误</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#promise-%E9%94%99%E8%AF%AF"><span class="nav-number">4.3.</span> <span class="nav-text">promise 错误</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#Vue-%E5%BC%82%E5%B8%B8"><span class="nav-number">4.4.</span> <span class="nav-text">Vue 异常</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#React-%E5%BC%82%E5%B8%B8"><span class="nav-number">4.5.</span> <span class="nav-text">React 异常</span></a></li>
                    </ol>
                  </li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%95%B0%E6%8D%AE%E4%B8%8A%E6%8A%A5%E6%96%B9%E6%A1%88"><span class="nav-number">5.</span> <span class="nav-text">数据上报方案</span></a>
                    <ol class="nav-child">
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#Beacon"><span class="nav-number">5.1.</span> <span class="nav-text">Beacon</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#Image"><span class="nav-number">5.2.</span> <span class="nav-text">Image</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%85%BC%E5%AE%B9%E6%96%B9%E6%A1%88"><span class="nav-number">5.3.</span> <span class="nav-text">兼容方案</span></a></li>
                    </ol>
                  </li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%9F%8B%E7%82%B9%E5%B7%A5%E4%BD%9C%E6%B5%81"><span class="nav-number">6.</span> <span class="nav-text">埋点工作流</span></a></li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%89%8D%E7%AB%AF%E5%AE%B9%E7%81%BE"><span class="nav-number">7.</span> <span class="nav-text">前端容灾</span></a>
                    <ol class="nav-child">
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#LocalStorage"><span class="nav-number">7.1.</span> <span class="nav-text">LocalStorage</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#CDN"><span class="nav-number">7.2.</span> <span class="nav-text">CDN</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#Service-Worker"><span class="nav-number">7.3.</span> <span class="nav-text">Service Worker</span></a></li>
                    </ol>
                  </li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%89%A9%E5%B1%95"><span class="nav-number">8.</span> <span class="nav-text">扩展</span></a>
                    <ol class="nav-child">
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#SPA"><span class="nav-number">8.1.</span> <span class="nav-text">SPA</span></a></li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%94%A8%E6%88%B7%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86"><span class="nav-number">8.2.</span> <span class="nav-text">用户信息收集</span></a>
                        <ol class="nav-child">
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#navigator"><span class="nav-number">8.2.1.</span> <span class="nav-text">navigator</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#UV%EF%BC%88Unique-visitor%EF%BC%89"><span class="nav-number">8.2.2.</span> <span class="nav-text">UV（Unique visitor）</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#PV%EF%BC%88Page-View%EF%BC%89"><span class="nav-number">8.2.3.</span> <span class="nav-text">PV（Page View）</span></a></li>
                        </ol>
                      </li>
                      <li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%A1%B5%E9%9D%A2%E5%81%9C%E7%95%99%E6%97%B6%E9%97%B4"><span class="nav-number">8.3.</span> <span class="nav-text">页面停留时间</span></a>
                        <ol class="nav-child">
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BC%A0%E7%BB%9F%E7%BD%91%E7%AB%99"><span class="nav-number">8.3.1.</span> <span class="nav-text">传统网站</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#SPA-1"><span class="nav-number">8.3.2.</span> <span class="nav-text">SPA</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%B5%8F%E8%A7%88%E6%B7%B1%E5%BA%A6"><span class="nav-number">8.3.3.</span> <span class="nav-text">浏览深度</span></a></li>
                          <li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%A1%B5%E9%9D%A2%E8%B7%B3%E8%BD%AC%E6%9D%A5%E6%BA%90"><span class="nav-number">8.3.4.</span> <span class="nav-text">页面跳转来源</span></a></li>
                        </ol>
                      </li>
                    </ol>
                  </li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E6%89%8B%E5%8A%A8%E4%B8%8A%E6%8A%A5DEMO"><span class="nav-number">9.</span> <span class="nav-text">手动上报DEMO</span></a></li>
                  <li class="nav-item nav-level-1"><a class="nav-link" href="#%E5%8F%82%E8%80%83"><span class="nav-number">10.</span> <span class="nav-text">参考</span></a></li>
                </ol>
              </div>
            </div>
            <!--/noindex-->

            <div class="site-overview-wrap sidebar-panel">
              <div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
                <p class="site-author-name" itemprop="name">holidaypenguin</p>
                <div class="site-description" itemprop="description"></div>
              </div>
              <div class="site-state-wrap site-overview-item animated">
                <nav class="site-state">
                  <div class="site-state-item site-state-posts">
                    <a href="/archives/">

                      <span class="site-state-item-count">138</span>
                      <span class="site-state-item-name">日志</span>
                    </a>
                  </div>
                  <div class="site-state-item site-state-categories">
                    <a href="/categories/">

                      <span class="site-state-item-count">26</span>
                      <span class="site-state-item-name">分类</span></a>
                  </div>
                  <div class="site-state-item site-state-tags">
                    <a href="/tags/">

                      <span class="site-state-item-count">234</span>
                      <span class="site-state-item-name">标签</span></a>
                  </div>
                </nav>
              </div>
              <div class="links-of-author site-overview-item animated">
                <span class="links-of-author-item">
                  <a href="https://github.com/holidaypenguin" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;holidaypenguin" rel="noopener" target="_blank"><i class="github fa-fw"></i>GitHub</a>
                </span>
                <span class="links-of-author-item">
                  <a href="mailto:songshipeng2016@gmail.com" title="E-Mail → mailto:songshipeng2016@gmail.com" rel="noopener" target="_blank"><i class="envelope fa-fw"></i>E-Mail</a>
                </span>
              </div>



            </div>
          </div>
        </div>
      </aside>
      <div class="sidebar-dimmer"></div>


    </header>


    <div class="back-to-top" role="button">
      <i class="fa fa-arrow-up"></i>
      <span>0%</span>
    </div>

    <noscript>
      <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
    </noscript>


    <div class="main-inner post posts-expand">





      <div class="post-block">



        <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="zh-CN">
          <link itemprop="mainEntityOfPage" href="https://holidaypenguin.gitee.io/blob/2021-09-14-front-end-data-embedding-point/">

          <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
            <meta itemprop="image" content="/images/avatar.gif">
            <meta itemprop="name" content="holidaypenguin">
            <meta itemprop="description" content="">
          </span>

          <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
            <meta itemprop="name" content="HolidayPenguin">
          </span>
          <header class="post-header">
            <h1 class="post-title" itemprop="name headline">
              前端数据埋点
            </h1>

            <div class="post-meta-container">
              <div class="post-meta">
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-calendar"></i>
                  </span>
                  <span class="post-meta-item-text">发表于</span>

                  <time title="创建时间：2021-09-14 14:48:07" itemprop="dateCreated datePublished" datetime="2021-09-14T14:48:07+08:00">2021-09-14</time>
                </span>
                <span class="post-meta-item">
                  <span class="post-meta-item-icon">
                    <i class="far fa-folder"></i>
                  </span>
                  <span class="post-meta-item-text">分类于</span>
                  <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                    <a href="/categories/FrontEnd/" itemprop="url" rel="index"><span itemprop="name">FrontEnd</span></a>
                  </span>
                </span>


                <span id="/blob/2021-09-14-front-end-data-embedding-point/" class="post-meta-item leancloud_visitors" data-flag-title="前端数据埋点" title="阅读次数">
                  <span class="post-meta-item-icon">
                    <i class="far fa-eye"></i>
                  </span>
                  <span class="post-meta-item-text">阅读次数：</span>
                  <span class="leancloud-visitors-count"></span>
                </span>
              </div>

              <div class="post-description">
                <!-- more -->
              </div>
            </div>
          </header>




          <div class="post-body" itemprop="articleBody">
            <p><a target="_blank" rel="noopener" href="https://github.com/jwenjian/visitor-count-badge"><img src="" data-original="https://visitor-badge.glitch.me/badge?page_id=holidaypenguin.gitee.io" alt="总访客数量"></a></p>
            <h1 id="什么是埋点"><a href="#什么是埋点" class="headerlink" title="什么是埋点"></a>什么是埋点</h1>
            <p>在线上项目中,需要统计产品中用户行为和使用情况，从而可以从用户和产品的角度去了解用户群体，从而升级和迭代产品，使其更加贴近用户。用户行为数据可以通过前端数据监控的方式获得，除此之外，前端还需要实现性能监控和异常监控。性能监控包括首屏加载时间、白屏时间、http请求时间和http响应时间。异常监控包括前端脚本执行报错等。<br>实现前端监控有三个步骤：前端埋点和上报、数据处理和数据分析。</p>
            <h2 id="埋点"><a href="#埋点" class="headerlink" title="埋点"></a>埋点</h2>
            <p>埋点，它的学名是事件追踪（Event Tracking），主要是针对特定用户行为或业务过程进行捕获、处理和发送的相关技术及实施过程。埋点是数据领域的一个专业术语，也是互联网领域的一个俗称。</p>
            <p>埋点是产品数据分析的基础，一般用于推荐系统的反馈、用户行为的监控和分析、新功能或者运营活动效果的统计分析等。指明了产品优化的方向。</p>
            <p>埋点包含两个重要概念：事件（event），属性（param）</p>
            <ul>
              <li>事件（event）：应用中发生了什么，例如用户操作、系统事件或系统错误。以你拍一产品为例，包含以下事件：enter_page（进入页面）、leave_page（离开页面）。</li>
              <li>属性（param）：为了描述用户群细分而定义的属性，例如语言偏好或地理位置。以“进入课后练习”事件为例，它包含如下事件属性：enter_from（从哪个页面来），class_id（课程id）等。</li>
              <li>属性值（value）：属性的维度，即行为触发时的具体维度。例如：enter_from：home（主页）、system（系统）等。</li>
            </ul>
            <h2 id="主流方案"><a href="#主流方案" class="headerlink" title="主流方案"></a>主流方案</h2>
            <h3 id="无痕埋点（全埋点）"><a href="#无痕埋点（全埋点）" class="headerlink" title="无痕埋点（全埋点）"></a>无痕埋点（全埋点）</h3>
            <p>利用浏览器或APP自带的监听方式，对用户的浏览页面、点击等行为进行收集，一般用于粗颗粒度的数据分析，例如公司的slardar。只需要简单的加载了一段定义好的SDK代码，技术门槛更低，使用与部署也简单，避免了需求变更，埋点错误导致的重新埋点。</p>
            <ul>
              <li>优点：<ul>
                  <li>接入简单，几乎无侵入，不需要额外的开发成本</li>
                  <li>用户操作行为收集非常完整，产品迭代过程中几乎不会遗漏</li>
                </ul>
              </li>
              <li>缺点：<ul>
                  <li>数据噪声大，不管有用没有，数据都会被收集，给数据传输和服务器增加压力</li>
                  <li>无法定制化埋点，无法采集到指定事件和业务属性</li>
                  <li>可供DA使用的信息较少</li>
                </ul>
              </li>
            </ul>
            <h3 id="可视化埋点"><a href="#可视化埋点" class="headerlink" title="可视化埋点"></a>可视化埋点</h3>
            <p>可视化埋点，也叫框架式埋点、无痕埋点。解决了纯手动埋点的开发成本和更新成本，通过可视化工具快速配置采集节点（圈点），在前端自动解析配置，并根据配置上传埋点数据，比起手动埋点看起来更无痕，这里的配置数据可以设置过滤条件，避免针对所有元素（比如全埋点），可以在调用开启自动监控API时通过设置一些特征属性，来过滤不符合条件的元素，实现只针对某些元素进行自动上报数据的需求。</p>
            <p>比如国外比较早做可视化的是Mixpanel，国内较早支持可视化埋点的有TalkingData、诸葛IO，2017年腾讯的MTA也宣布支持可视化埋点；相比于手动埋点更新困难，埋点成本高的问题，可视化埋点优化了移动运营中数据采集的流程，能够支持产品运营随时调整埋点，无需再走发版流程，直接把配置结果推入到前端，数据采集流程更简化，也更方便产品的迭代。</p>
            <p>可视化埋点配置化能力相对手动埋点更强，是对手动埋点的补充而不是代替，很多手动埋点点都可以通过好的规划和架构变为可视化埋点。</p>
            <h3 id="手动埋点"><a href="#手动埋点" class="headerlink" title="手动埋点"></a>手动埋点</h3>
            <p>手动埋点，也叫代码埋点，即纯手动写代码。调用埋点 SDK 的函数，在需要埋点的业务逻辑功能位置调用接口，上报埋点数据，像友盟、百度统计等第三方数据统计服务商大都采用这种方案。</p>
            <ul>
              <li>优点：<ul>
                  <li>可以精确埋点，具备明确的事件标识</li>
                  <li>业务属性非常丰富</li>
                  <li>埋点触发方式可以灵活定义</li>
                  <li>DA使用更方便和精确</li>
                </ul>
              </li>
              <li>缺点：<ul>
                  <li>工作量大，而且对代码侵入性很大，后期维护也不是很方便，容易出现手动差错，如果错误，重新埋点的成本也很高。</li>
                </ul>
              </li>
            </ul>
            <h3 id="埋点方案比较"><a href="#埋点方案比较" class="headerlink" title="埋点方案比较"></a>埋点方案比较</h3>
            <p>开发初期相对来说自动埋点好一点，当产品达到一个相对稳定阶段或者有特殊情况时可以使用手动埋点；<br>可视化埋点在有能力的情况下可以接入。</p>
            <h2 id="常见埋点属性"><a href="#常见埋点属性" class="headerlink" title="常见埋点属性"></a>常见埋点属性</h2>
            <p>通常前端是按照页面维度统计埋点的，常见的事件属性如下：</p>
            <table>
              <thead>
                <tr>
                  <th>属性</th>
                  <th>描述</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>uid</td>
                  <td>用户id，若用户未登陆，则返回特定标识id</td>
                </tr>
                <tr>
                  <td>url</td>
                  <td>当前事件触发页面的url</td>
                </tr>
                <tr>
                  <td>eventTime</td>
                  <td>触发埋点的时间戳</td>
                </tr>
                <tr>
                  <td>localTime</td>
                  <td>触发埋点时的用户本地时间，使用标准YYYY-MM-DD HH:mm:ss格式表示，方便后期直接使用字符串查询</td>
                </tr>
                <tr>
                  <td>deviceType</td>
                  <td>当前用户使用的设备类型，比如apple、三星、chrome等</td>
                </tr>
                <tr>
                  <td>deviceId</td>
                  <td>当前用户使用的设备id</td>
                </tr>
                <tr>
                  <td>osType</td>
                  <td>当前用户使用的系统类型，比如windows、macos、ios、android等</td>
                </tr>
                <tr>
                  <td>osVersion</td>
                  <td>当前用户使用的系统版本</td>
                </tr>
                <tr>
                  <td>appVersion</td>
                  <td>当前应用版本</td>
                </tr>
                <tr>
                  <td>appId</td>
                  <td>当前应用id</td>
                </tr>
                <tr>
                  <td>extra</td>
                  <td>自定义数据，一般是序列化的字符串，且数据结构应保持稳定</td>
                </tr>
              </tbody>
            </table>
            <h2 id="常见埋点事件"><a href="#常见埋点事件" class="headerlink" title="常见埋点事件"></a>常见埋点事件</h2>
            <table>
              <thead>
                <tr>
                  <th>事件</th>
                  <th>上报时机</th>
                  <th>描述</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>页面停留</td>
                  <td>当前页面切换或者页面卸载时</td>
                  <td>记录前一页浏览时间</td>
                </tr>
                <tr>
                  <td>pv</td>
                  <td>进入页面时</td>
                  <td>页面访问次数，uv只需要根据deviceId过滤</td>
                </tr>
                <tr>
                  <td>交互事件</td>
                  <td>用户交互事件触发时</td>
                  <td>比如点击、长按等</td>
                </tr>
                <tr>
                  <td>逻辑事件</td>
                  <td>符合逻辑条件时</td>
                  <td>比如登陆、跳转页面等</td>
                </tr>
              </tbody>
            </table>
            <h2 id="埋点类型"><a href="#埋点类型" class="headerlink" title="埋点类型"></a>埋点类型</h2>
            <p>前端监控可以分为三类：数据监控、性能监控和异常监控。下面我们来一一的了解。</p>
            <h1 id="数据埋点"><a href="#数据埋点" class="headerlink" title="数据埋点"></a>数据埋点</h1>
            <p>数据监控，顾名思义就是监听用户的行为。常见的数据监控包括：</p>
            <ul>
              <li>PV访问来量（Page View）</li>
              <li>UV访问数（Unique Visitor）</li>
              <li>记录操作系统和浏览器</li>
              <li>记录用户在页面的停留时间</li>
              <li>用户在相应的页面中触发的行为</li>
              <li>用户通过什么入口来访问该网页</li>
            </ul>
            <p>统计这些数据是有意义的，比如我们知道了用户来源的渠道，可以促进产品的推广，知道用户在每一个页面停留的时间，可以针对停留较长的页面，增加广告推送等等</p>
            <h1 id="性能埋点"><a href="#性能埋点" class="headerlink" title="性能埋点"></a>性能埋点</h1>
            <p>目前性能指标数据大部分来源于 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/API/Performance">window.performance</a> API。</p>
            <h2 id="Performance-timing"><a href="#Performance-timing" class="headerlink" title="Performance.timing"></a>Performance.timing</h2>
            <p><img src="" data-original="/images/FrontEnd/point/84e6af0463f742ccbd2e98a0578aeeb3tplv-k3u1fbpfcp-watermark.awebp" alt="Performance.timing"></p>
            <p><img src="" data-original="/images/FrontEnd/point/258749b333514ff5b465585dde8180datplv-k3u1fbpfcp-watermark.awebp" alt="Performance.timing"></p>
            <table>
              <thead>
                <tr>
                  <th>参数名</th>
                  <th>描述</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>connectEnd</td>
                  <td>HTTP（TCP） 返回浏览器与服务器之间的连接建立时的时间戳。如果建立的是持久连接，则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。</td>
                </tr>
                <tr>
                  <td>connectStart</td>
                  <td>HTTP（TCP） 域名查询结束的时间戳。如果使用了持续连接(persistent connection)，或者这个信息存储到了缓存或者本地资源上，这个值将和 fetchStart一致。</td>
                </tr>
                <tr>
                  <td>domComplete</td>
                  <td>当前文档解析完成，即Document.readyState 变为 ‘complete’且相对应的readystatechange 被触发时的时间戳</td>
                </tr>
                <tr>
                  <td>domContentLoadedEventEnd</td>
                  <td>当所有需要立即执行的脚本已经被执行（不论执行顺序）时的时间戳。</td>
                </tr>
                <tr>
                  <td>domContentLoadedEventStart</td>
                  <td>当解析器发送DOMContentLoaded 事件，即所有需要被执行的脚本已经被解析时的时间戳。</td>
                </tr>
                <tr>
                  <td>domInteractive</td>
                  <td>当前网页DOM结构结束解析、开始加载内嵌资源时（即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时）的时间戳。</td>
                </tr>
                <tr>
                  <td>domLoading</td>
                  <td>当前网页DOM结构开始解析时（即Document.readyState属性变为“loading”、相应的 readystatechange事件触发时）的时间戳。</td>
                </tr>
                <tr>
                  <td>domainLookupEnd</td>
                  <td>DNS 域名查询完成的时间。如果使用了本地缓存（即无 DNS 查询）或持久连接，则与 fetchStart 值相等</td>
                </tr>
                <tr>
                  <td>domainLookupStart</td>
                  <td>DNS 域名查询开始的UNIX时间戳。如果使用了持续连接(persistent connection)，或者这个信息存储到了缓存或者本地资源上，这个值将和fetchStart一致。</td>
                </tr>
                <tr>
                  <td>fetchStart</td>
                  <td>浏览器准备好使用HTTP请求来获取(fetch)文档的时间戳。这个时间点会在检查任何应用缓存之前。</td>
                </tr>
                <tr>
                  <td>loadEventEnd</td>
                  <td>当load事件结束，即加载事件完成时的时间戳。如果这个事件还未被发送，或者尚未完成，它的值将会是0.</td>
                </tr>
                <tr>
                  <td>loadEventStart</td>
                  <td>load事件被发送时的时间戳。如果这个事件还未被发送，它的值将会是0。</td>
                </tr>
                <tr>
                  <td>navigationStart</td>
                  <td>同一个浏览器上一个页面卸载(unload)结束时的时间戳。如果没有上一个页面，这个值会和fetchStart相同。</td>
                </tr>
                <tr>
                  <td>redirectEnd</td>
                  <td>最后一个HTTP重定向完成时（也就是说是HTTP响应的最后一个比特直接被收到的时间）的时间戳。如果没有重定向，或者重定向中的一个不同源，这个值会返回0.</td>
                </tr>
                <tr>
                  <td>redirectStart</td>
                  <td>第一个HTTP重定向开始时的时间戳。如果没有重定向，或者重定向中的一个不同源，这个值会返回0。</td>
                </tr>
                <tr>
                  <td>requestStart</td>
                  <td>返回浏览器向服务器发出HTTP请求时（或开始读取本地缓存时）的时间戳。</td>
                </tr>
                <tr>
                  <td>responseEnd</td>
                  <td>返回浏览器从服务器收到（或从本地缓存读取，或从本地资源读取）最后一个字节时（如果在此之前HTTP连接已经关闭，则返回关闭时）的时间戳。</td>
                </tr>
                <tr>
                  <td>responseStart</td>
                  <td>返回浏览器从服务器收到（或从本地缓存读取）第一个字节时的时间戳。如果传输层在开始请求之后失败并且连接被重开，该属性将会被数制成新的请求的相对应的发起时间</td>
                </tr>
                <tr>
                  <td>secureConnectionStart</td>
                  <td>HTTPS 返回浏览器与服务器开始安全链接的握手时的时间戳。如果当前网页不要求安全连接，则返回0。</td>
                </tr>
                <tr>
                  <td>unloadEventEnd</td>
                  <td>和 unloadEventStart 相对应，unload事件处理完成时的时间戳。如果没有上一个页面,这个值会返回0。</td>
                </tr>
                <tr>
                  <td>unloadEventStart</td>
                  <td>上一个页面unload事件抛出时的时间戳。如果没有上一个页面，这个值会返回0。</td>
                </tr>
              </tbody>
            </table>
            <h2 id="常见性能指标"><a href="#常见性能指标" class="headerlink" title="常见性能指标"></a>常见性能指标</h2>
            <table>
              <thead>
                <tr>
                  <th>指标名</th>
                  <th>描述</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>FP</td>
                  <td>页面首次绘制时间</td>
                </tr>
                <tr>
                  <td>FCP</td>
                  <td>页面首次有内容绘制的时间</td>
                </tr>
                <tr>
                  <td>FMP</td>
                  <td>页面首次有效绘制时间，FMP &gt;= FCP</td>
                </tr>
                <tr>
                  <td>TTI</td>
                  <td>页面完全可交互时间</td>
                </tr>
                <tr>
                  <td>FID</td>
                  <td>页面加载阶段，用户首次交互操作的延时时间</td>
                </tr>
                <tr>
                  <td>MPFID</td>
                  <td>页面加载阶段，用户交互操作可能遇到的最大延时时间</td>
                </tr>
                <tr>
                  <td>LOAD</td>
                  <td>页面完全加载的时间（load 事件发生的时间）</td>
                </tr>
              </tbody>
            </table>
            <h3 id="FP"><a href="#FP" class="headerlink" title="FP"></a>FP</h3>
            <p>FP (First Paint)指标通常会反映页面的白屏时间，而白屏时间会反映当前 Web 页面的网络加载性能情况，当加载性能非常良好的情况下，白屏的时间就会越短，用户等待内容的时间就会越短，流失的概率就会降低。</p>
            <p>该指标可以通过 <code>performance.getEntriesByType(&#39;paint&#39;)</code> 方法获取 <code>PerformancePaintTiming API</code> 提供的打点信息，找到 name 为 first-paint 的对象，描述的即为 FP 的指标数据，如下图所示：</p>
            <p><img src="" data-original="/images/FrontEnd/point/858b21fcc1af46f4b897c9964fde9e4etplv-k3u1fbpfcp-watermark.awebp" alt="在这里插入图片描述"></p>
            <h3 id="FCP"><a href="#FCP" class="headerlink" title="FCP"></a>FCP</h3>
            <p>FCP (First Contentful Paint) 为首次有内容渲染的时间点，在性能统计指标中，从用户开始访问 Web 页面的时间点到 FCP 的时间点这段时间可以被视为无内容时间，一般 FCP &gt;= FP。</p>
            <p>该指标可以通过 <code>performance.getEntriesByType(&#39;paint&#39;)</code> 方法获取 PerformancePaintTiming API 提供的打点信息，找到 name 为 first-contentful-paint 的对象，描述的即为 FCP 的指标数据，如下图所示：</p>
            <p><img src="" data-original="/images/FrontEnd/point/234df87c541a44e4826fbd744d894316tplv-k3u1fbpfcp-watermark.awebp" alt="在这里插入图片描述"></p>
            <h3 id="FMP"><a href="#FMP" class="headerlink" title="FMP"></a>FMP</h3>
            <p>FMP（First Meaningful Paint），即首次绘制有意义内容的时间，当整体页面的布局和文字内容全部渲染完成后，即可认为是完成了首次有意义内容的绘制。所以 FMP 衡量了用户看到网页的主要内容的时间，是用户体验角度的一种重要的衡量指标。</p>
            <p>前端业界现在比较认可的一个计算 FMP 的方式就是「<strong>页面在加载和渲染过程中最大布局变动之后的那个绘制时间</strong> 」。可通过 MutationObserver 监听每一次页面整体的 DOM 变化，触发 MutationObserver 的回调，在回调计算出当前 DOM 树的变动分数，分数变化最剧烈的时刻，即为 FMP 的时间点。</p>
            <h3 id="TTI"><a href="#TTI" class="headerlink" title="TTI"></a>TTI</h3>
            <p>TTI（Time To Interactive），即<strong>从页面加载开始到页面处于完全可交互状态所花费的时间</strong>。页面处于完全可交互状态时，满足以下 3 个条件：</p>
            <ol>
              <li>页面已经显示有用内容。</li>
              <li>页面上的可见元素关联的事件响应函数已经完成注册。</li>
              <li>事件响应函数可以在事件发生后的 50ms 内开始执行。</li>
            </ol>
            <h2 id="资源加载指标"><a href="#资源加载指标" class="headerlink" title="资源加载指标"></a>资源加载指标</h2>
            <p><code>window.performance.getEntriesByType(&#39;resource&#39;)</code>会返回当前页面加载的所有资源（js、css、img…）的各类性能指标，可用于静态资源性能数据采集。</p>
            <p><img src="" data-original="/images/FrontEnd/point/785fe910dc77480b815fcc8e40e67526tplv-k3u1fbpfcp-watermark.awebp" alt="在这里插入图片描述"></p>
            <p>主要类型有：script、link、img、css、xmlhttprequest、beacon、fetch、other。 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">PerformanceResourceTiming - Web APIs | MDN</a></p>
            <table>
              <thead>
                <tr>
                  <th>参数名</th>
                  <th>描述</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>connectEnd</td>
                  <td>一个 DOMHighResTimeStamp，表示浏览器完成建立与服务器的连接以检索资源之后的时间。</td>
                </tr>
                <tr>
                  <td>connectStart</td>
                  <td>一个 DOMHighResTimeStamp，表示浏览器开始建立与服务器的连接以检索资源之前的时间。</td>
                </tr>
                <tr>
                  <td>decodedBodySize</td>
                  <td>一个 number，表示在删除任何应用的内容编码之后，从消息主体的请求（HTTP 或缓存）中接收到的大小（以八位字节为单位）。</td>
                </tr>
                <tr>
                  <td>domainLookupEnd</td>
                  <td>一个 DOMHighResTimeStamp，表示浏览器完成资源的域名查找之后的时间。</td>
                </tr>
                <tr>
                  <td>domainLookupStart</td>
                  <td>一个 DOMHighResTimeStamp，表示在浏览器立即开始资源的域名查找之前的时间</td>
                </tr>
                <tr>
                  <td>duration</td>
                  <td>返回一个 timestamp，即 responseEnd 和 startTime 属性的差值。</td>
                </tr>
                <tr>
                  <td>encodedBodySize</td>
                  <td>一个 number，表示在删除任何应用的内容编码之前，从有效内容主体的请求（HTTP 或缓存）中接收到的大小（以八位字节为单位）。</td>
                </tr>
                <tr>
                  <td>entryType</td>
                  <td>返回 “resource”。</td>
                </tr>
                <tr>
                  <td>fetchStart</td>
                  <td>一个 DOMHighResTimeStamp，表示浏览器即将开始获取资源之前的时间。</td>
                </tr>
                <tr>
                  <td>initiatorType</td>
                  <td>一个 string，代表启动性能条目的资源的类型</td>
                </tr>
                <tr>
                  <td>name</td>
                  <td>返回资源 URL。</td>
                </tr>
                <tr>
                  <td>nextHopProtocol</td>
                  <td>一个 string，代表用于获取资源的网络协议，由 <a target="_blank" rel="noopener" href="https://tools.ietf.org/html/rfc7301">ALPN 协议 ID（RFC7301）</a> 定义。</td>
                </tr>
                <tr>
                  <td>redirectEnd</td>
                  <td>一个 DOMHighResTimeStamp，表示收到上一次重定向响应的发送最后一个字节时的时间。</td>
                </tr>
                <tr>
                  <td>redirectStart</td>
                  <td>一个 DOMHighResTimeStamp 代表启动重定向的请求开始之前的时间。</td>
                </tr>
                <tr>
                  <td>requestStart</td>
                  <td>一个 DOMHighResTimeStamp，表示浏览器开始向服务器请求资源之前的时间。</td>
                </tr>
                <tr>
                  <td>responseEnd</td>
                  <td>一个 DOMHighResTimeStamp，表示在浏览器接收到资源的最后一个字节之后或在传输连接关闭之前（以先到者为准）的时间。</td>
                </tr>
                <tr>
                  <td>responseStart</td>
                  <td>一个 DOMHighResTimeStamp，表示浏览器从服务器接收到响应的第一个字节后的时间。</td>
                </tr>
                <tr>
                  <td>secureConnectionStart</td>
                  <td>一个 DOMHighResTimeStamp，表示浏览器即将开始握手过程以保护当前连接之前的时间。</td>
                </tr>
                <tr>
                  <td>serverTiming</td>
                  <td>一个 PerformanceServerTiming 数组，包含服务器计时指标的 PerformanceServerTiming 条目。</td>
                </tr>
                <tr>
                  <td>startTime</td>
                  <td>返回一个 timestamp，表示资源获取开始的时间。该值等效于 fetchStart。</td>
                </tr>
                <tr>
                  <td>transferSize</td>
                  <td>一个 number 代表所获取资源的大小（以八位字节为单位）。该大小包括响应标头字段以及响应有效内容主体。</td>
                </tr>
                <tr>
                  <td>workerStart</td>
                  <td>一个 DOMHighResTimeStamp， 如果服务 Worker 线程已经在运行，则返回在分派 FetchEvent 之前的时间戳，如果尚未运行，则返回在启动 Service Worker 线程之前的时间戳。如果服务 Worker 未拦截该资源，则该属性将始终返回 0。</td>
                </tr>
              </tbody>
            </table>
            <h2 id="其他指标计算方式"><a href="#其他指标计算方式" class="headerlink" title="其他指标计算方式"></a>其他指标计算方式</h2>
            <table>
              <thead>
                <tr>
                  <th>指标名</th>
                  <th>描述</th>
                  <th>计算方式</th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <td>DNS查询</td>
                  <td>DNS 阶段耗时</td>
                  <td>domainLookupEnd - domainLookupStart</td>
                </tr>
                <tr>
                  <td>TCP连接</td>
                  <td>TCP 阶段耗时</td>
                  <td>connectEnd - connectStart</td>
                </tr>
                <tr>
                  <td>SSL建连</td>
                  <td>SSL 连接时间</td>
                  <td>connectEnd - secureConnectionStart</td>
                </tr>
                <tr>
                  <td>首字节网络请求</td>
                  <td>首字节响应时间（ttfb）</td>
                  <td>responseStart - requestStart</td>
                </tr>
                <tr>
                  <td>内容传输</td>
                  <td>内容传输，Response阶段耗时</td>
                  <td>responseEnd - responseStart</td>
                </tr>
                <tr>
                  <td>DOM解析</td>
                  <td>Dom解析时间</td>
                  <td>domInteractive - responseEnd</td>
                </tr>
                <tr>
                  <td>资源加载</td>
                  <td>资源加载</td>
                  <td>loadEventStart - domContentLoadedEventEnd</td>
                </tr>
                <tr>
                  <td>首字节</td>
                  <td>首字节</td>
                  <td>responseStart - fetchStart</td>
                </tr>
                <tr>
                  <td>DOM Ready</td>
                  <td>dom ready</td>
                  <td>domContentLoadedEventEnd - fetchStart</td>
                </tr>
                <tr>
                  <td>redirect时间</td>
                  <td>重定向时间</td>
                  <td>redirectEnd - redirectStart</td>
                </tr>
                <tr>
                  <td>DOM render</td>
                  <td>dom渲染耗时</td>
                  <td>domComplete - domLoading</td>
                </tr>
                <tr>
                  <td>load</td>
                  <td>页面加载耗时</td>
                  <td>loadEventEnd - navigationStart</td>
                </tr>
                <tr>
                  <td>unload</td>
                  <td>页面卸载耗时</td>
                  <td>unloadEventEnd - unloadEventStart</td>
                </tr>
                <tr>
                  <td>请求耗时</td>
                  <td>请求耗时</td>
                  <td>responseEnd - requestStart</td>
                </tr>
                <tr>
                  <td>白屏时间</td>
                  <td>白屏时间</td>
                  <td>domLoading - navigationStart</td>
                </tr>
              </tbody>
            </table>
            <h1 id="异常埋点"><a href="#异常埋点" class="headerlink" title="异常埋点"></a>异常埋点</h1>
            <p>目前所能捕捉的错误有三种:</p>
            <h2 id="资源加载错误"><a href="#资源加载错误" class="headerlink" title="资源加载错误"></a>资源加载错误</h2>
            <p>通过 <code>addEventListener(&#39;error&#39;, callback, true) </code>在捕获阶段捕捉资源加载失败错误。</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="comment">// 在捕获阶段，捕获资源加载失败错误</span></span><br><span class="line">addEventListener(<span class="string">&#x27;error&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> target = e.target</span><br><span class="line">    <span class="keyword">if</span> (target != <span class="built_in">window</span>) &#123;</span><br><span class="line">        monitor.errors.push(&#123;</span><br><span class="line">            type: target.localName,</span><br><span class="line">            url: target.src || target.href,</span><br><span class="line">            msg: (target.src || target.href) + <span class="string">&#x27; is load error&#x27;</span>,</span><br><span class="line">            time: <span class="built_in">Date</span>.now()</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="literal">true</span>)</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <h2 id="js-执行错误"><a href="#js-执行错误" class="headerlink" title="js 执行错误"></a>js 执行错误</h2>
            <p>通过 <code>window.onerror</code> 捕捉 js 错误。</p>
            <ul>
              <li>跨域的脚本会给出 “Script Error.” 提示，拿不到具体的错误信息和堆栈信息。此时需要在script标签增加<code>crossorigin=&quot;anonymous&quot;</code>属性，同时资源服务器需要增加CORS相关配置，比如<code>Access-Control-Allow-Origin: *</code></li>
            </ul>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="comment">// 监听 js 错误</span></span><br><span class="line"><span class="built_in">window</span>.onerror = <span class="function"><span class="keyword">function</span>(<span class="params">msg, url, row, col, error</span>) </span>&#123;</span><br><span class="line">    monitor.errors.push(&#123;</span><br><span class="line">        type: <span class="string">&#x27;javascript&#x27;</span>,</span><br><span class="line">        row: row,</span><br><span class="line">        col: col,</span><br><span class="line">        msg: error &amp;&amp; error.stack? error.stack : msg,</span><br><span class="line">        url: url,</span><br><span class="line">        time: <span class="built_in">Date</span>.now()</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <h2 id="promise-错误"><a href="#promise-错误" class="headerlink" title="promise 错误"></a>promise 错误</h2>
            <p>通过 <code>addEventListener(&#39;unhandledrejection&#39;, callback)</code>捕捉 promise 错误，但是没有发生错误的行数，列数等信息，只能手动抛出相关错误信息。</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="comment">// 监听 promise 错误 缺点是获取不到行数数据</span></span><br><span class="line">addEventListener(<span class="string">&#x27;unhandledrejection&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span><br><span class="line">    monitor.errors.push(&#123;</span><br><span class="line">        type: <span class="string">&#x27;promise&#x27;</span>,</span><br><span class="line">        msg: (e.reason &amp;&amp; e.reason.msg) || e.reason || <span class="string">&#x27;&#x27;</span>,</span><br><span class="line">        time: <span class="built_in">Date</span>.now()</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;)</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <h2 id="Vue-异常"><a href="#Vue-异常" class="headerlink" title="Vue 异常"></a>Vue 异常</h2>
            <p><strong>errorHandle</strong></p>
            <p>Vue为组件呈现函数和监视程序期间没有捕获的错误分配的一个处理程序。不过这个方法一旦捕获取错误后，错误就不会抛到控制台</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line">Vue.config.errorHandler = <span class="function">(<span class="params">err, vm, info</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// err 错误处理</span></span><br><span class="line">    <span class="comment">// vm vue实例</span></span><br><span class="line">    <span class="comment">// info 是特定于vue的错误信息，比如哪个生命周期勾子</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果需要把错误抛到控制台，需要在这里加上这一行</span></span><br><span class="line">    <span class="built_in">console</span>.error(err)</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <p><strong>warnHandle</strong></p>
            <p>是Vue警告分配一个自定义处理程序。不过只在开发环境有效，生产环境会被自忽略</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line">Vue.config.warnHandle = (msg, vm, trace)&#123;</span><br><span class="line">    <span class="comment">// trace 是组件层次结构</span></span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <p><strong>renderError</strong></p>
            <p>默认的渲染函数遇到错误时，提供了一个代替渲染输出的。这个和热重新加载一起用会很棒</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="keyword">new</span> Vue(&#123;</span><br><span class="line">    render (h)&#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">&#x27;oops&#x27;</span>)</span><br><span class="line">    &#125;,</span><br><span class="line">    renderError (h, err)&#123;</span><br><span class="line">        <span class="keyword">return</span> h(<span class="string">&#x27;per&#x27;</span>,&#123; <span class="attr">style</span>: &#123; <span class="attr">color</span>: red &#125; &#125;, err.stack)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;).$mount(<span class="string">&#x27;#app&#x27;</span>)</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <p><strong>errorCaptured</strong></p>
            <p>任何派生组件捕获错误时调用。它可以 return false 来阻止错误传播。可以在这个勾子里修改组件状态。不过如果是在模板或呈现函数里有条件语句，在捕获到错误时，这些条件语句会短路，可能进入一个无限渲染循环</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line">Vue.component(<span class="string">&#x27;ErrorBoundary&#x27;</span>,&#123;</span><br><span class="line">    data: <span class="function">() =&gt;</span> &#123; ... &#125;</span><br><span class="line">    <span class="function"><span class="title">errorCaptured</span>(<span class="params">err, vm, info</span>)</span>&#123;</span><br><span class="line">        <span class="comment">// err 错误信息  </span></span><br><span class="line">        <span class="comment">// vm 触发错误的组件实例 </span></span><br><span class="line">        <span class="comment">// info 错误捕获位置信息</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <h2 id="React-异常"><a href="#React-异常" class="headerlink" title="React 异常"></a>React 异常</h2>
            <p><strong>getDerivedStateFromError</strong></p>
            <p>React 也有自带的捕获所有子组件中错误的方法，这个生命周期会在后代组件抛出错误时被调用。注意这个是在渲染阶段调用的，所以不允许出现副作用</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ErrorBoundary</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(props)</span><br><span class="line">        <span class="built_in">this</span>.state = &#123; <span class="attr">hasError</span>: <span class="literal">false</span> &#125;</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">static</span> <span class="function"><span class="title">getDerivedStateFromError</span>(<span class="params">error</span>)</span> &#123;</span><br><span class="line">        <span class="comment">// 更新 state 使下一次渲染可以显降级 UI</span></span><br><span class="line">        <span class="keyword">return</span> &#123; <span class="attr">hasError</span>: <span class="literal">true</span> &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <p><strong>componentDidCatch</strong></p>
            <p>这个生命周期也会在后代组件抛出错误时被调用，但是不会捕获事件处理器和异步代码的异常。它会在【提交】阶段被调用，所以允许出现副作用</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ErrorBoundary</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="title">constructor</span>(<span class="params">props</span>)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(props)</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="function"><span class="title">componentDidCatch</span>(<span class="params">error, info</span>)</span>&#123;</span><br><span class="line">        <span class="comment">// error 错误信息</span></span><br><span class="line">        <span class="comment">// info.componentStack 错误组件位置</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <h1 id="数据上报方案"><a href="#数据上报方案" class="headerlink" title="数据上报方案"></a>数据上报方案</h1>
            <p>在这个场景中，需要考虑两个问题：</p>
            <ul>
              <li>如果数据上报接口与业务系统使用同一域名，浏览器对请求并发量有限制，所以存在网络资源竞争的可能性。</li>
              <li>浏览器通常在页面卸载时会忽略异步ajax请求，如果需要必须进行数据请求，一般在unload或者beforeunload事件中创建同步ajax请求，以此延迟页面卸载。从用户侧角度，就是页面跳转变慢。</li>
            </ul>
            <h2 id="Beacon"><a href="#Beacon" class="headerlink" title="Beacon"></a>Beacon</h2>
            <p><img src="" data-original="/images/FrontEnd/point/8b4b4d8b2afc4b1dbd32a50281a9e86dtplv-k3u1fbpfcp-watermark.awebp" alt="Beacon API"></p>
            <p>可以看到，除开ie浏览器，目前主流现代浏览器对beacon的支持率非常高。<a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/API/Beacon_API/Using_the_Beacon_API">Beacon - MDN文档</a></p>
            <p>Beacon 接口用来调度向 Web 服务器发送的异步非阻塞请求。</p>
            <ul>
              <li>Beacon 请求使用 HTTP <code>POST</code>方法，并且不需要有响应。</li>
              <li>Beacon 请求能确保在页面触发 unload 之前完成初始化。</li>
            </ul>
            <p>通俗的讲就是，Beacon可将数据异步发送至服务端，且能够保证在页面卸载完成前发送请求（解决ajax页面卸载会终止请求的问题）。 使用方法如下：</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line">navigator.sendBeacon(url, data);</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <p>其中 data 参数是可选的，它的类型可以为 <code>ArrayBufferView</code>, <code>Blob</code>, <code>DOMString</code> 或者 <code>FormData</code>。如果浏览器成功地将 beacon 请求加入到待发送的队列里，这个方法将会返回 true ，否则将会返回 false</p>
            <p>使用Beacon时需要后台需要使用<code>post</code>方法接收参数，考虑到跨域问题，后台还需要改造接口配置CORS。同时请求头必须满足<a target="_blank" rel="noopener" href="https://fetch.spec.whatwg.org/#cors-safelisted-request-header">CORS-safelisted request-header</a>，其中content-type的类型必须为<code>application/x-www-form-urlencoded</code>, <code>multipart/form-data</code>, 或者<code>text/plain</code>。</p>
            <figure class="highlight ts">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="keyword">type</span> ContentType = <span class="string">&#x27;application/x-www-form-urlencoded&#x27;</span> | <span class="string">&#x27;multipart/form-data&#x27;</span> | <span class="string">&#x27;text/plain&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> serilizeParams = <span class="function">(<span class="params">params: <span class="built_in">object</span></span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">window</span>.btoa(<span class="built_in">JSON</span>.stringify(params))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sendBeacon</span>(<span class="params">url: <span class="built_in">string</span>, params: <span class="built_in">object</span></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> formData = <span class="keyword">new</span> FormData()</span><br><span class="line">  formData.append(<span class="string">&#x27;params&#x27;</span>, serilizeParams(params))</span><br><span class="line">  navigator.sendBeacon(url, formData)</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <h2 id="Image"><a href="#Image" class="headerlink" title="Image"></a>Image</h2>
            <p>sendBeacon的兼容性问题是不可避免的，不过可以充分利用大部分浏览器会在页面卸载前完成图片的加载的特性，通过在页面添加img的方式上报数据。</p>
            <figure class="highlight ts">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sendImage</span>(<span class="params">url: <span class="built_in">string</span>, params: <span class="built_in">object</span></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> img = <span class="keyword">new</span> Image()</span><br><span class="line"></span><br><span class="line">  img.style.display = <span class="string">&#x27;none&#x27;</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> removeImage = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    img.parentNode.removeChild(img)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  img.onload = removeImage</span><br><span class="line">  img.onerror = removeImage</span><br><span class="line"></span><br><span class="line">  img.src = <span class="string">`<span class="subst">$&#123;url&#125;</span>?params=<span class="subst">$&#123;serilizeParams(params)&#125;</span>`</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">document</span>.body.appendChild(img)</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <p>由于img图片为get请求方式，不同服务器针对uri的长度有限制，长度超过限制时会出现HTTP 414错误，所以还要注意上报频率，减少一次性上传的属性过多。</p>
            <blockquote>
              <p>HTTP 1.1 defines Status Code 414 Request-URI Too Long for the cases where a server-defined limit is reached. You can see further details on RFC 2616. For the case of client-defined limits, there is no sense on the server returning something, because the server won’t receive the request at all.</p>
            </blockquote>
            <h2 id="兼容方案"><a href="#兼容方案" class="headerlink" title="兼容方案"></a>兼容方案</h2>
            <p>优先使用sendBeacon的方式，Image方式作为fallback。</p>
            <figure class="highlight ts">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sendLog</span>(<span class="params">url: <span class="built_in">string</span>, params: <span class="built_in">object</span></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span>（navigator.sendBeacon) &#123;</span><br><span class="line">        sendBeacon(url, params)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        sendImage(url, params)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>


            <h1 id="埋点工作流"><a href="#埋点工作流" class="headerlink" title="埋点工作流"></a>埋点工作流</h1>
            <p>开发埋点功能首先需要明确埋点需求，然后要规定一套自己的埋点规范（命名规范、流程规范）。</p>
            <h1 id="前端容灾"><a href="#前端容灾" class="headerlink" title="前端容灾"></a>前端容灾</h1>
            <p>前端容灾指的因为各种原因后端接口挂了(比如服务器断电断网等等)，前端依然能保证页面信息能完整展示。</p>
            <p>比如 banner 或者列表之类的等等数据是从接口获取的，要是接口获取不到了，怎么办呢？</p>
            <h2 id="LocalStorage"><a href="#LocalStorage" class="headerlink" title="LocalStorage"></a>LocalStorage</h2>
            <p>首先，使用 LocalStorage</p>
            <p>在接口正常返回的时候把数据都存到 LocalStorage ，可以把接口路径作为 key，返回的数据作为 value</p>
            <p>然后之次再请求，只要请求失败，就读取 LocalStorage，把上次的数据拿出来展示，并上报错误信息，以获得缓冲时间</p>
            <h2 id="CDN"><a href="#CDN" class="headerlink" title="CDN"></a>CDN</h2>
            <p>同时，每次更新都要备份一份静态数据放到CDN</p>
            <p>在接口请求失败的时候，并且 LocalStorage 也没有数据的情况下，就去 CDN 摘取备份的静态数据</p>
            <h2 id="Service-Worker"><a href="#Service-Worker" class="headerlink" title="Service Worker"></a>Service Worker</h2>
            <p>假如不只是接口数据，整个 html 都想存起来，就可以使用 Service Worker 做离线存储</p>
            <p>利用 Service Worker 的请求拦截，不管是存接口数据，还是存页面静态资源文件都可以</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="comment">// 拦截所有请求事件 缓存中有请求的数据就直接用缓存，否则去请求数据 </span></span><br><span class="line">self.addEventListener(<span class="string">&#x27;fetch&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123; </span><br><span class="line">    <span class="comment">// 查找request中被缓存命中的response </span></span><br><span class="line">    e.respondWith(caches.match(e.request).then( <span class="function"><span class="params">response</span> =&gt;</span> &#123; </span><br><span class="line">        <span class="keyword">if</span> (response) &#123; </span><br><span class="line">            <span class="keyword">return</span> response </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">&#x27;fetch source&#x27;</span>) </span><br><span class="line">    &#125;)) </span><br><span class="line">&#125;)</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <h1 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h1>
            <h2 id="SPA"><a href="#SPA" class="headerlink" title="SPA"></a>SPA</h2>
            <p><code>window.performance</code> API 是有缺点的，在 SPA 切换路由时，<code>window.performance.timing</code> 的数据不会更新。 所以我们需要另想办法来统计切换路由到加载完成的时间。 拿 Vue 举例，一个可行的办法就是切换路由时，在路由的全局前置守卫 <code>beforeEach</code> 里获取开始时间，在组件的 <code>mounted</code> 钩子里执行 <code>vm.$nextTick </code> 函数来获取组件的渲染完毕时间。</p>
            <figure class="highlight js">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line">router.beforeEach(<span class="function">(<span class="params">to, <span class="keyword">from</span>, next</span>) =&gt;</span> &#123;</span><br><span class="line">	store.commit(<span class="string">&#x27;setPageLoadedStartTime&#x27;</span>, <span class="keyword">new</span> <span class="built_in">Date</span>())</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="title">mounted</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">	<span class="built_in">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">		<span class="built_in">this</span>.$store.commit(<span class="string">&#x27;setPageLoadedTime&#x27;</span>, <span class="keyword">new</span> <span class="built_in">Date</span>() - <span class="built_in">this</span>.$store.state.pageLoadedStartTime)</span><br><span class="line">	&#125;)</span><br><span class="line">&#125;</span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>

            <p>除了性能和错误监控，其实我们还可以收集更多的信息。</p>
            <h2 id="用户信息收集"><a href="#用户信息收集" class="headerlink" title="用户信息收集"></a>用户信息收集</h2>
            <h3 id="navigator"><a href="#navigator" class="headerlink" title="navigator"></a>navigator</h3>
            <p>使用 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/API/Window/navigator">window.navigator</a> 可以收集到用户的设备信息，操作系统，浏览器信息…</p>
            <h3 id="UV（Unique-visitor）"><a href="#UV（Unique-visitor）" class="headerlink" title="UV（Unique visitor）"></a>UV（Unique visitor）</h3>
            <p>是指通过互联网浏览这个网页的访客，00:00-24:00 内相同的设备访问只被计算一次。一天内同个访客多次访问仅计算一个 UV。</p>
            <p>在用户访问网站时，可以生成一个随机字符串+时间日期，保存在本地。在网页发生请求时（如果超过当天24小时，则重新生成），把这些参数传到后端，后端利用这些信息生成 UV 统计报告。</p>
            <h3 id="PV（Page-View）"><a href="#PV（Page-View）" class="headerlink" title="PV（Page View）"></a>PV（Page View）</h3>
            <p>即页面浏览量或点击量，用户每 1 次对网站中的每个网页访问均被记录 1 个PV。用户对同一页面的多次访问，访问量累计，用以衡量网站用户访问的网页数量。</p>
            <h2 id="页面停留时间"><a href="#页面停留时间" class="headerlink" title="页面停留时间"></a>页面停留时间</h2>
            <h3 id="传统网站"><a href="#传统网站" class="headerlink" title="传统网站"></a>传统网站</h3>
            <p>用户在进入 A 页面时，通过后台请求把用户进入页面的时间捎上。过了 10 分钟，用户进入 B 页面，这时后台可以通过接口捎带的参数可以判断出用户在 A 页面停留了 10 分钟。</p>
            <h3 id="SPA-1"><a href="#SPA-1" class="headerlink" title="SPA"></a>SPA</h3>
            <p>可以利用 router 来获取用户停留时间，拿 Vue 举例，通过 <code>router.beforeEach</code>、<code>destroyed</code> 这两个钩子函数来获取用户停留该路由组件的时间。</p>
            <h3 id="浏览深度"><a href="#浏览深度" class="headerlink" title="浏览深度"></a>浏览深度</h3>
            <p>通过 <code>document.documentElement.scrollTop</code> 属性以及屏幕高度，可以判断用户是否浏览完网站内容。</p>
            <h3 id="页面跳转来源"><a href="#页面跳转来源" class="headerlink" title="页面跳转来源"></a>页面跳转来源</h3>
            <p>通过 <code>document.referrer</code> 属性，可以知道用户是从哪个网站跳转而来。</p>
            <h1 id="手动上报DEMO"><a href="#手动上报DEMO" class="headerlink" title="手动上报DEMO"></a>手动上报DEMO</h1>
            <figure class="highlight html">
              <table>
                <tr>
                  <td class="gutter">
                    <pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br></pre>
                  </td>
                  <td class="code">
                    <pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">&quot;X-UA-Compatible&quot;</span> <span class="attr">content</span>=<span class="string">&quot;ie=edge&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript">        <span class="function"><span class="keyword">function</span> <span class="title">monitorInit</span>(<span class="params"></span>) </span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="keyword">const</span> monitor = &#123;</span></span><br><span class="line"><span class="javascript">                <span class="comment">// 数据上传地址</span></span></span><br><span class="line"><span class="javascript">                url: <span class="string">&#x27;&#x27;</span>,</span></span><br><span class="line"><span class="javascript">                <span class="comment">// 性能信息</span></span></span><br><span class="line">                performance: &#123;&#125;,</span><br><span class="line"><span class="javascript">                <span class="comment">// 资源信息</span></span></span><br><span class="line">                resources: &#123;&#125;,</span><br><span class="line"><span class="javascript">                <span class="comment">// 错误信息</span></span></span><br><span class="line">                errors: [],</span><br><span class="line"><span class="javascript">                <span class="comment">// 用户信息</span></span></span><br><span class="line">                user: &#123;</span><br><span class="line"><span class="javascript">                    <span class="comment">// 屏幕宽度</span></span></span><br><span class="line">                    screen: screen.width,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 屏幕高度</span></span></span><br><span class="line">                    height: screen.height,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 浏览器平台</span></span></span><br><span class="line">                    platform: navigator.platform,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 浏览器的用户代理信息</span></span></span><br><span class="line">                    userAgent: navigator.userAgent,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 浏览器用户界面的语言</span></span></span><br><span class="line">                    language: navigator.language,</span><br><span class="line">                &#125;,</span><br><span class="line"><span class="javascript">                <span class="comment">// 手动添加错误</span></span></span><br><span class="line"><span class="javascript">                <span class="function"><span class="title">addError</span>(<span class="params">error</span>)</span> &#123;</span></span><br><span class="line"><span class="javascript">                    <span class="keyword">const</span> obj = &#123;&#125;</span></span><br><span class="line"><span class="javascript">                    <span class="keyword">const</span> &#123; type, msg, url, row, col &#125; = error</span></span><br><span class="line">                    if (type) obj.type = type</span><br><span class="line">                    if (msg) obj.msg = msg</span><br><span class="line">                    if (url) obj.url = url</span><br><span class="line">                    if (row) obj.row = row</span><br><span class="line">                    if (col) obj.col = col</span><br><span class="line"><span class="javascript">                    obj.time = <span class="keyword">new</span> <span class="built_in">Date</span>().getTime()</span></span><br><span class="line">                    monitor.errors.push(obj)</span><br><span class="line">                &#125;,</span><br><span class="line"><span class="javascript">                <span class="comment">// 重置 monitor 对象</span></span></span><br><span class="line"><span class="javascript">                <span class="function"><span class="title">reset</span>(<span class="params"></span>)</span> &#123;</span></span><br><span class="line"><span class="javascript">                    <span class="built_in">window</span>.performance &amp;&amp; <span class="built_in">window</span>.performance.clearResourceTimings()</span></span><br><span class="line">                    monitor.performance = getPerformance()</span><br><span class="line">                    monitor.resources = getResources()</span><br><span class="line">                    monitor.errors = []</span><br><span class="line">                &#125;,</span><br><span class="line"><span class="javascript">                <span class="comment">// 清空 error 信息</span></span></span><br><span class="line"><span class="javascript">                <span class="function"><span class="title">clearError</span>(<span class="params"></span>)</span> &#123;</span></span><br><span class="line">                    monitor.errors = []</span><br><span class="line">                &#125;,</span><br><span class="line"><span class="javascript">                <span class="comment">// 上传监控数据</span></span></span><br><span class="line"><span class="javascript">                <span class="function"><span class="title">upload</span>(<span class="params"></span>)</span> &#123;</span></span><br><span class="line"><span class="javascript">                    <span class="comment">// 自定义上传</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">// axios.post(&#123;</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">//     url: monitor.url,</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">//     data: &#123;</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">//         performance,</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">//         resources,</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">//         errors,</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">//         user,</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">//     &#125;</span></span></span><br><span class="line"><span class="javascript">                    <span class="comment">// &#125;)</span></span></span><br><span class="line">                &#125;,</span><br><span class="line"><span class="javascript">                <span class="comment">// 设置数据上传地址</span></span></span><br><span class="line"><span class="javascript">                <span class="function"><span class="title">setURL</span>(<span class="params">url</span>)</span> &#123;</span></span><br><span class="line">                    monitor.url = url</span><br><span class="line">                &#125;,</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">            <span class="comment">// 获取性能信息</span></span></span><br><span class="line"><span class="javascript">            <span class="keyword">const</span> getPerformance = <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                <span class="keyword">if</span> (!<span class="built_in">window</span>.performance) <span class="keyword">return</span></span></span><br><span class="line"><span class="javascript">                <span class="keyword">const</span> timing = <span class="built_in">window</span>.performance.timing</span></span><br><span class="line"><span class="javascript">                <span class="keyword">const</span> performance = &#123;</span></span><br><span class="line"><span class="javascript">                    <span class="comment">// 重定向耗时</span></span></span><br><span class="line">                    redirect: timing.redirectEnd - timing.redirectStart,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 白屏时间</span></span></span><br><span class="line">                    whiteScreen: whiteScreen,</span><br><span class="line"><span class="javascript">                    <span class="comment">// DOM 渲染耗时</span></span></span><br><span class="line">                    dom: timing.domComplete - timing.domLoading,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 页面加载耗时</span></span></span><br><span class="line">                    load: timing.loadEventEnd - timing.navigationStart,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 页面卸载耗时</span></span></span><br><span class="line">                    unload: timing.unloadEventEnd - timing.unloadEventStart,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 请求耗时</span></span></span><br><span class="line">                    request: timing.responseEnd - timing.requestStart,</span><br><span class="line"><span class="javascript">                    <span class="comment">// 获取性能信息时当前时间</span></span></span><br><span class="line"><span class="javascript">                    time: <span class="keyword">new</span> <span class="built_in">Date</span>().getTime(),</span></span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">                <span class="keyword">return</span> performance</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">            <span class="comment">// 获取资源信息</span></span></span><br><span class="line"><span class="javascript">            <span class="keyword">const</span> getResources = <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                <span class="keyword">if</span> (!<span class="built_in">window</span>.performance) <span class="keyword">return</span></span></span><br><span class="line"><span class="javascript">                <span class="keyword">const</span> data = <span class="built_in">window</span>.performance.getEntriesByType(<span class="string">&#x27;resource&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                <span class="keyword">const</span> resource = &#123;</span></span><br><span class="line">                    xmlhttprequest: [],</span><br><span class="line">                    css: [],</span><br><span class="line">                    other: [],</span><br><span class="line">                    script: [],</span><br><span class="line">                    img: [],</span><br><span class="line">                    link: [],</span><br><span class="line">                    fetch: [],</span><br><span class="line"><span class="javascript">                    <span class="comment">// 获取资源信息时当前时间</span></span></span><br><span class="line"><span class="javascript">                    time: <span class="keyword">new</span> <span class="built_in">Date</span>().getTime(),</span></span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">                data.forEach(<span class="function"><span class="params">item</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                    <span class="keyword">const</span> arry = resource[item.initiatorType]</span></span><br><span class="line">                    arry &amp;&amp; arry.push(&#123;</span><br><span class="line"><span class="javascript">                        <span class="comment">// 资源的名称</span></span></span><br><span class="line">                        name: item.name,</span><br><span class="line"><span class="javascript">                        <span class="comment">// 资源加载耗时</span></span></span><br><span class="line">                        duration: item.duration.toFixed(2),</span><br><span class="line"><span class="javascript">                        <span class="comment">// 资源大小</span></span></span><br><span class="line">                        size: item.transferSize,</span><br><span class="line"><span class="javascript">                        <span class="comment">// 资源所用协议</span></span></span><br><span class="line">                        protocol: item.nextHopProtocol,</span><br><span class="line">                    &#125;)</span><br><span class="line">                &#125;)</span><br><span class="line"></span><br><span class="line"><span class="javascript">                <span class="keyword">return</span> resource</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">            <span class="built_in">window</span>.onload = <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                <span class="comment">// 在浏览器空闲时间获取性能及资源信息 https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback</span></span></span><br><span class="line"><span class="javascript">                <span class="keyword">if</span> (<span class="built_in">window</span>.requestIdleCallback) &#123;</span></span><br><span class="line"><span class="javascript">                    <span class="built_in">window</span>.requestIdleCallback(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line">                        monitor.performance = getPerformance()</span><br><span class="line">                        monitor.resources = getResources()</span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(<span class="string">&#x27;页面性能信息&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(monitor.performance)</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(<span class="string">&#x27;页面资源信息&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(monitor.resources)</span></span><br><span class="line">                    &#125;)</span><br><span class="line"><span class="javascript">                &#125; <span class="keyword">else</span> &#123;</span></span><br><span class="line"><span class="javascript">                    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line">                        monitor.performance = getPerformance()</span><br><span class="line">                        monitor.resources = getResources()</span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(<span class="string">&#x27;页面性能信息&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(monitor.performance)</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(<span class="string">&#x27;页面资源信息&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                        <span class="built_in">console</span>.log(monitor.resources)</span></span><br><span class="line">                    &#125;, 0)</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">            <span class="comment">// 捕获资源加载失败错误 js css img...</span></span></span><br><span class="line"><span class="javascript">            addEventListener(<span class="string">&#x27;error&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">                <span class="keyword">const</span> target = e.target</span></span><br><span class="line"><span class="javascript">                <span class="keyword">if</span> (target != <span class="built_in">window</span>) &#123;</span></span><br><span class="line">                    monitor.errors.push(&#123;</span><br><span class="line">                        type: target.localName,</span><br><span class="line">                        url: target.src || target.href,</span><br><span class="line"><span class="javascript">                        msg: (target.src || target.href) + <span class="string">&#x27; is load error&#x27;</span>,</span></span><br><span class="line"><span class="javascript">                        <span class="comment">// 错误发生的时间</span></span></span><br><span class="line"><span class="javascript">                        time: <span class="keyword">new</span> <span class="built_in">Date</span>().getTime(),</span></span><br><span class="line">                    &#125;)</span><br><span class="line"></span><br><span class="line"><span class="javascript">                    <span class="built_in">console</span>.log(<span class="string">&#x27;所有的错误信息&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                    <span class="built_in">console</span>.log(monitor.errors)</span></span><br><span class="line">                &#125;</span><br><span class="line"><span class="javascript">            &#125;, <span class="literal">true</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="javascript">            <span class="comment">// 监听 js 错误</span></span></span><br><span class="line"><span class="javascript">            <span class="built_in">window</span>.onerror = <span class="function"><span class="keyword">function</span>(<span class="params">msg, url, row, col, error</span>) </span>&#123;</span></span><br><span class="line">                monitor.errors.push(&#123;</span><br><span class="line"><span class="javascript">                    type: <span class="string">&#x27;javascript&#x27;</span>, <span class="comment">// 错误类型</span></span></span><br><span class="line"><span class="javascript">                    row: row, <span class="comment">// 发生错误时的代码行数</span></span></span><br><span class="line"><span class="javascript">                    col: col, <span class="comment">// 发生错误时的代码列数</span></span></span><br><span class="line"><span class="javascript">                    msg: error &amp;&amp; error.stack? error.stack : msg, <span class="comment">// 错误信息</span></span></span><br><span class="line"><span class="javascript">                    url: url, <span class="comment">// 错误文件</span></span></span><br><span class="line"><span class="javascript">                    time: <span class="keyword">new</span> <span class="built_in">Date</span>().getTime(), <span class="comment">// 错误发生的时间</span></span></span><br><span class="line">                &#125;)</span><br><span class="line"></span><br><span class="line"><span class="javascript">                <span class="built_in">console</span>.log(<span class="string">&#x27;所有的错误信息&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                <span class="built_in">console</span>.log(monitor.errors)</span></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">            <span class="comment">// 监听 promise 错误 缺点是获取不到行数数据</span></span></span><br><span class="line"><span class="javascript">            addEventListener(<span class="string">&#x27;unhandledrejection&#x27;</span>, <span class="function"><span class="params">e</span> =&gt;</span> &#123;</span></span><br><span class="line">                monitor.errors.push(&#123;</span><br><span class="line"><span class="javascript">                    type: <span class="string">&#x27;promise&#x27;</span>,</span></span><br><span class="line"><span class="javascript">                    msg: (e.reason &amp;&amp; e.reason.msg) || e.reason || <span class="string">&#x27;&#x27;</span>,</span></span><br><span class="line"><span class="javascript">                    <span class="comment">// 错误发生的时间</span></span></span><br><span class="line"><span class="javascript">                    time: <span class="keyword">new</span> <span class="built_in">Date</span>().getTime(),</span></span><br><span class="line">                &#125;)</span><br><span class="line"></span><br><span class="line"><span class="javascript">                <span class="built_in">console</span>.log(<span class="string">&#x27;所有的错误信息&#x27;</span>)</span></span><br><span class="line"><span class="javascript">                <span class="built_in">console</span>.log(monitor.errors)</span></span><br><span class="line">            &#125;)</span><br><span class="line"></span><br><span class="line"><span class="javascript">            <span class="keyword">return</span> monitor</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> monitor = monitorInit()</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;stylesheet&quot;</span> <span class="attr">href</span>=<span class="string">&quot;test.css&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>手动上报DEMO<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;btn1&quot;</span>&gt;</span>错误测试按钮1<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;btn2&quot;</span>&gt;</span>错误测试按钮2<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;btn3&quot;</span>&gt;</span>错误测试按钮3<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;https://avatars3.githubusercontent.com/u/22117876?s=460&amp;v=4&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;test.png&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;192.168.10.15/test.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.querySelector(<span class="string">&#x27;.btn1&#x27;</span>).onclick = <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">    <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">        <span class="built_in">console</span>.log(button)</span></span><br><span class="line">    &#125;, 0)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.querySelector(<span class="string">&#x27;.btn2&#x27;</span>).onclick = <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span></span><br><span class="line">        reject(&#123;</span><br><span class="line"><span class="javascript">            msg: <span class="string">&#x27;test.js promise is error&#x27;</span></span></span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.querySelector(<span class="string">&#x27;.btn3&#x27;</span>).onclick = <span class="function">() =&gt;</span> &#123;</span></span><br><span class="line"><span class="javascript">    <span class="keyword">throw</span> (<span class="string">&#x27;这是一个手动扔出的错误&#x27;</span>)</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre>
                  </td>
                </tr>
              </table>
            </figure>
            <h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1>
            <ul>
              <li><a target="_blank" rel="noopener" href="https://juejin.cn/post/6938075086737899534#heading-1">前端埋点与监控方案</a></li>
              <li><a target="_blank" rel="noopener" href="https://juejin.cn/post/6899430989404045320">深入了解前端监控原理</a></li>
              <li><a target="_blank" rel="noopener" href="https://juejin.cn/post/6995235136547586084">前端异常监控和容灾</a></li>
              <li><a target="_blank" rel="noopener" href="https://juejin.cn/post/6987681953424080926">一篇讲透自研的前端错误监控</a></li>
              <li><a target="_blank" rel="noopener" href="https://fex.baidu.com/blog/2014/05/build-performance-monitor-in-7-days/">7 天打造前端性能监控系统</a></li>
              <li><a target="_blank" rel="noopener" href="https://github.com/wangweianger/zanePerfor">zanePerfor</a></li>
            </ul>

          </div>





          <footer class="post-footer">
            <div class="post-tags">
              <a href="/tags/FrontEnd/" rel="tag"># FrontEnd</a>
              <a href="/tags/%E5%9F%8B%E7%82%B9/" rel="tag"># 埋点</a>
            </div>



            <div class="post-nav">
              <div class="post-nav-item">
                <a href="/blob/2021-08-31-the-role-of-webpack-dev-server-host-0.0.0.0/" rel="prev" title="webpack-dev-server host 0.0.0.0 的作用">
                  <i class="fa fa-chevron-left"></i> webpack-dev-server host 0.0.0.0 的作用
                </a>
              </div>
              <div class="post-nav-item">
              </div>
            </div>
          </footer>
        </article>
      </div>







      <script>
        window.addEventListener('tabs:register', () => {
          let {
            activeClass
          } = CONFIG.comments;
          if (CONFIG.comments.storage) {
            activeClass = localStorage.getItem('comments_active') || activeClass;
          }
          if (activeClass) {
            const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
            if (activeTab) {
              activeTab.click();
            }
          }
        });
        if (CONFIG.comments.storage) {
          window.addEventListener('tabs:click', event => {
            if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
            const commentClass = event.target.classList[1];
            localStorage.setItem('comments_active', commentClass);
          });
        }
      </script>
    </div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


      <div class="copyright">
        &copy;
        <span itemprop="copyrightYear">2021</span>
        <span class="with-love">
          <i class="fa fa-heart"></i>
        </span>
        <span class="author" itemprop="copyrightHolder">holidaypenguin</span>
      </div>
      <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/mist/" class="theme-link" rel="noopener" target="_blank">NexT.Mist</a> 强力驱动
      </div>

    </div>
  </footer>


  <script size="300" alpha="0.1" zIndex="-1" src="https://cdn.jsdelivr.net/npm/ribbon.js@1.0.2/dist/ribbon.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
  <script src="/js/love.js"></script>

  <script src="/js/development.js"></script>
  <script src="/js/utils.js"></script>
  <script src="/js/motion.js"></script>
  <script src="/js/schemes/muse.js"></script>
  <script src="/js/next-boot.js"></script>


  <script src="/js/local-search.js"></script>









  <script>
    (function() {
      function leancloudSelector(url) {
        url = encodeURI(url);
        return document.getElementById(url).querySelector('.leancloud-visitors-count');
      }

      function addCount(Counter) {
        const visitors = document.querySelector('.leancloud_visitors');
        const url = decodeURI(visitors.id);
        const title = visitors.dataset.flagTitle;

        Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({
            url
          })))
          .then(response => response.json())
          .then(({
            results
          }) => {
            if (results.length > 0) {
              const counter = results[0];
              leancloudSelector(url).innerText = counter.time + 1;
              Counter('put', '/classes/Counter/' + counter.objectId, {
                  time: {
                    '__op': 'Increment',
                    'amount': 1
                  }
                })
                .catch(error => {
                  console.error('Failed to save visitor count', error);
                });
            } else {
              Counter('post', '/classes/Counter', {
                  title,
                  url,
                  time: 1
                })
                .then(response => response.json())
                .then(() => {
                  leancloudSelector(url).innerText = 1;
                })
                .catch(error => {
                  console.error('Failed to create', error);
                });
            }
          })
          .catch(error => {
            console.error('LeanCloud Counter Error', error);
          });
      }

      function showTime(Counter) {
        const visitors = document.querySelectorAll('.leancloud_visitors');
        const entries = [...visitors].map(element => {
          return decodeURI(element.id);
        });

        Counter('get', '/classes/Counter?where=' + encodeURIComponent(JSON.stringify({
            url: {
              '$in': entries
            }
          })))
          .then(response => response.json())
          .then(({
            results
          }) => {
            for (let url of entries) {
              const target = results.find(item => item.url === url);
              leancloudSelector(url).innerText = target ? target.time : 0;
            }
          })
          .catch(error => {
            console.error('LeanCloud Counter Error', error);
          });
      }

      const {
        app_id,
        app_key,
        server_url
      } = {
        "enable": true,
        "app_id": "povuqNsSqFodlakVIwtEX5kb-gzGzoHsz",
        "app_key": "zXD40RDtDB3DMtpC89k0AK7g",
        "server_url": null,
        "security": false
      };

      function fetchData(api_server) {
        const Counter = (method, url, data) => {
          return fetch(`${api_server}/1.1${url}`, {
            method,
            headers: {
              'X-LC-Id': app_id,
              'X-LC-Key': app_key,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify(data)
          });
        };
        if (CONFIG.page.isPost) {
          if (CONFIG.hostname !== location.hostname) return;
          addCount(Counter);
        } else if (document.querySelectorAll('.post-title-link').length >= 1) {
          showTime(Counter);
        }
      }

      const api_server = app_id.slice(-9) === '-MdYXbMMI' ? `https://${app_id.slice(0, 8).toLowerCase()}.api.lncldglobal.com` : server_url;

      if (api_server) {
        fetchData(api_server);
      } else {
        fetch('https://app-router.leancloud.cn/2/route?appId=' + app_id)
          .then(response => response.json())
          .then(({
            api_server
          }) => {
            fetchData('https://' + api_server);
          });
      }
    })();
  </script>



<script>
            window.imageLazyLoadSetting = {
                isSPA: false,
                preloadRatio: 1,
                processImages: null,
            };
        </script><script>window.addEventListener("load",function(){var t=/\.(gif|jpg|jpeg|tiff|png)$/i,r=/^data:image\/[a-z]+;base64,/;Array.prototype.slice.call(document.querySelectorAll("img[data-original]")).forEach(function(a){var e=a.parentNode;"A"===e.tagName&&(e.href.match(t)||e.href.match(r))&&(e.href=a.dataset.original)})});</script><script>!function(n){n.imageLazyLoadSetting.processImages=o;var e=n.imageLazyLoadSetting.isSPA,i=n.imageLazyLoadSetting.preloadRatio||1,r=Array.prototype.slice.call(document.querySelectorAll("img[data-original]"));function o(){e&&(r=Array.prototype.slice.call(document.querySelectorAll("img[data-original]")));for(var t,a=0;a<r.length;a++)0<=(t=(t=r[a]).getBoundingClientRect()).bottom&&0<=t.left&&t.top<=(n.innerHeight*i||document.documentElement.clientHeight*i)&&function(){var t,e,n,i,o=r[a];t=o,e=function(){r=r.filter(function(t){return o!==t})},n=new Image,i=t.getAttribute("data-original"),n.onload=function(){t.src=i,e&&e()},t.src!==i&&(n.src=i)}()}o(),n.addEventListener("scroll",function(){var t,e;t=o,e=n,clearTimeout(t.tId),t.tId=setTimeout(function(){t.call(e)},500)})}(this);</script></body>

</html>